package model.editormodel;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Observable;

import javax.swing.JOptionPane;
import javax.xml.XMLConstants;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import locsi.UserSettings;
import model.LogicType;

import org.xml.sax.SAXException;

import view.UtilityConstants;
import controller.commoncontroller.UtilityDialogs;

public class LocsiParser extends Observable {

	/**
	 * Tabulator
	 */
	private static final String tab = "  ";

	/**
	 * Der XML-Datei Header
	 */
	private static final String xmlHead = "<?xml version='1.0' encoding='UTF-8'?>"
			+ UtilityConstants.NEWLINE;

	/**
	 * Pfad zur XSD-Schema-Datei
	 */
	public static final String XSD_SCHEME = UtilityConstants.LOCSI_DATA
			+ "circuits-1.0.xsd";
	
	/**
	 * XSD-Schema File Objekt
	 */
	public static File schemaFile;

	private EditorModel eModel;

	/**
	 * Map die beim Speichern einer Schaltung/selbstdefiniertes Bauteil befuellt
	 * wird. Immer wenn man auf ein selbstdefiniertes Bauteil stoesst wird das
	 * zugehoerige cModel hier reingelegt.
	 */
	private Map<String, CircuitModel> cModelMapToSave;

	/**
	 * Der Einfachheit halber wird die Map noch redundant als Liste gespeichert.
	 */
	private List<CircuitModel> cModelListToSave;

	/**
	 * Liste mit Strings die die Namen der mit getCmodlOfSelfDefRecursive
	 * aufgerufenen circuits speichert. (Um Schleife zu vermeiden)
	 */
	private List<String> listOfCircuitsToLoad;

	/**
	 * Konstruktor des Parsers der nur eine Referenz auf das aktuell gueltige
	 * <code>EditorModel</code> hat.
	 * 
	 * @param eModel
	 *            - <code>EditorModel</code> - aktuell gueltiges EditorModel
	 * @throws <code>IllegalArgumentException</code> - falls eModel null ist
	 */

	public LocsiParser(EditorModel eModel) {
		if (eModel != null) {
			this.eModel = eModel;
		} else {
			this.setChanged();
			this.notifyObservers("Programmfehler!");
			throw new IllegalArgumentException("eModel darf nicht null sein");
		}
	}

	/**
	 * Methode zum Einlesen eines selbstdefinierten Bauteils. Gibt das cModel
	 * des Bauteils zurueck sofern keine Fehler waehrend dessen auftreten. Das
	 * cModel hat noch alle Verbindungen in sich, also auch die von den
	 * Schaltern zu den Bauteilen, bzw von den Bauteilen zu den Lampen. Diese
	 * Verbindungen werden dann erst beim Einbau in ein selbstdefiniertes
	 * Bauteil getrennt (Die von Schalter zu Bauteil bleibt bestehen, umgedreht
	 * nicht, Lampen-Verbindung bleibt komplett erhalten)
	 * 
	 * Benutzt <code>saveCircuit</code>.
	 * 
	 * @param location
	 *            Pfad zur XML-Datei des selbstdefinierten Bauteils - also auch
	 *            mit Dateiname, allerdings ohne .xml-Endung
	 * @return <code>CircuitModel</code> - das cModel des selbstdefinierten
	 *         Bauteils inklusive der Schalter/Lampen-Verbindungen (siehe oben)
	 *         falls das eingelesene Bauteil kein vollstaendiges Bauteil ist,
	 *         oder an der Stelle keine entsprechende Datei gefunden werden
	 *         kann, wird null zurueckgegeben
	 */
	protected CircuitModel readSelfDefinedModel(String location) {
		CircuitModel cModel = readCircuit(location
				+ UtilityDialogs.XML_EXTENSION);
		if (cModel != null && cModel.isComponent()) {
			return cModel;
		}
		return null;
	}

	/**
	 * Liest an einem bestimmten Pfad eine Schaltung ein, die nicht zwingend
	 * vollstaendig sein muss.
	 * 
	 * @param path
	 *            - Dateipfad von der die Schaltung eingelesen werden soll
	 * @return <code>CircuitModel</code> - falls eine Schaltung gelesen werden
	 *         konnte, wird das zugehoerige cModel zurueckgegeben
	 */
	public CircuitModel readCircuit(String path) {

		// zuerst pruefen ob die Datei mit dem xsd-Schema uebereinstimmt
		// hier wird auch abgefragt ob path null ist und ob die datei
		// ueberhaupt existiert
		// EDIT: hier wird noch gefragt ob man die schaltung validieren mag
		// oder nicht... und beim einfuegen von selbstdefinierten bauteilen
		// wird immer validiert (indem ueberprueft wird, ob im zu lesenden pfad
		// selfdefined_gates vorkommt
		if (path.contains("selfdefined_gates")
				|| (UserSettings.askForValidateMode != -1 && (UserSettings.askForValidateMode == 1 || (UserSettings.askForValidateMode == 0 && JOptionPane
						.showConfirmDialog(
								UtilityDialogs.getFrame(),
								"<html><body><h1>Soll die Datei validiert werden?</h1><br><b><u>Achtung:</u></b> Wenn nicht validiert wird, verkürzt das die Einlesezeit (bei Schaltungen > 10.000 Bauteilen)<br> erheblich, allerdings kann beim Lesen einer ungültigen Datei das Programm abstürzen.</body></html>",
								"Validierung...", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION)))) {
			if (!isValidXML(path)) {
				return null;
			}
		}
		XMLInputFactory factory = XMLInputFactory.newInstance();
		XMLStreamReader parser;
		String mainName = "";
		boolean foundMainCircuit = false;
		CircuitModel mainCmodel = null;
		listOfCircuitsToLoad = new ArrayList<String>();
		try {
			parser = factory.createXMLStreamReader(new FileInputStream(path));
			while (parser.hasNext()) {
				switch (parser.getEventType()) {
				case XMLStreamConstants.START_DOCUMENT:
					break;

				case XMLStreamConstants.END_DOCUMENT:
					parser.close();
					break;

				// vorgehensweise: 1.lesen des namens der Hauptschaltung
				// 2. suchen des circuit-Tag mit dem Namen der Hauptschaltung
				// 3. es wird nur die hauptschaltung gelesen
				// 4. wird in der Hauptschaltung ein selbstdefiniertes Bauteil
				// gefunden wird in einer rekursiven methode
				// (getCmodelOfSelfDefRecursive)
				// das CircuitModel des SD-Bauteils gelesen und auch wieder
				// nur der entsprechende tag gesucht/gelesen
				case XMLStreamConstants.START_ELEMENT:
					String tagName = parser.getLocalName();
					if (tagName.equals("circuits")) {

						// hier wird der name der Hauptschaltung festgelegt
						// der normalerweise auch dem Dateinamen entspricht
						mainName = parser.getAttributeValue(null, "main");
					} else if (tagName.equals("circuit")) {
						String circuitName = parser.getAttributeValue(null,
								"name");

						// hinzufuegen des circuitNamens zur liste
						listOfCircuitsToLoad.add(circuitName);

						// wenn der gefundene circuit-Tag den Namen der
						// Hauptschaltung hat, wird angefangen zu lesen
						// (foundMainCircuit = true)
						if (circuitName.equals(mainName)) {
							mainCmodel = new CircuitModel();
							mainCmodel.setName(mainName);
							foundMainCircuit = true;
						} else {
							foundMainCircuit = false;
							break;
						}

						// wenn man sich im richtigen circuitTag befindet und
						// man auf einen component-Tag stoeßt, wird das
						// component ins cModel eingefuegt
					} else if (foundMainCircuit && tagName.equals("component")) {
						String type = parser.getAttributeValue(null, "type");
						LogicType lType = xmlToLogicType(type, parser);
						String name = parser.getAttributeValue(null, "name");

						// bei einem normalen bauteil wird ganz normal
						// eingefuegt
						if (lType != LogicType.SELFDEFINED) {
							mainCmodel.addLogicElement(lType, name, Double
									.parseDouble(parser.getAttributeValue(null,
											"posx")), Double.parseDouble(parser
									.getAttributeValue(null, "posy")), null);
						} else {
							CircuitModel sdCmodel;

							// falls man auf missing-circuit stoesst, heisst das
							// dass der Parser in einer anderen xml Datei nach
							// dem Bauteil suchen soll, wenn er die nicht finden
							// kann wird das ganze Lesen abgebrochen und null
							// zurueckgegeben
							if (type.equals("missing-circuit")) {
								sdCmodel = readSelfDefinedModel(UtilityConstants.SD_DIRECTORY
										+ parser.getAttributeValue(null,
												"type2"));
								if (sdCmodel == null) {
									return null;
								}
							} else {

								// SD-Bauteil wurde gefunden -> rekursiv wieder
								// die xml-datei nach dem zugehoerigen
								// circuit-tag durch suchen
								String circTagName = parser.getAttributeValue(
										null, "type2");

								// wenn man eh schon rekursiv nach dem namen
								// sucht, kann nicht
								// wieder getCModelofselfdefrecursive aufgerufen
								// werden
								// weil man sonst in einer endlosschleife ist
								if (listOfCircuitsToLoad.contains(circTagName)) {
									this.setChanged();
									this.notifyObservers("Fehler beim Laden wegen Abhängigkeiten die nicht verarbeitet werden können!");
									return null;
								}
								sdCmodel = getCmodelOfSelfDefRecursive(path,
										circTagName);
							}
							mainCmodel
									.addLogicElement(lType, name, Double
											.parseDouble(parser
													.getAttributeValue(null,
															"posx")), Double
											.parseDouble(parser
													.getAttributeValue(null,
															"posy")), sdCmodel);

						}

						// wenn man auf einen connection-tag stoesst wird eine
						// connection zwischen dem input und output hergestellt,
						// mit der zugehoerigen methode im circuitModel
					} else if (foundMainCircuit && tagName.equals("connection")) {
						String source = parser
								.getAttributeValue(null, "source");
						String sourcePort = parser.getAttributeValue(null,
								"sourcePort");
						String target = parser
								.getAttributeValue(null, "target");
						String targetPort = parser.getAttributeValue(null,
								"targetPort");
						mainCmodel.setConnection(source, sourcePort, target,
								targetPort);
					}

					break;

				case XMLStreamConstants.END_ELEMENT:
					break;

				case XMLStreamConstants.NAMESPACE:
					break;

				case XMLStreamConstants.CHARACTERS:
					break;

				default:
					break;
				}
				parser.next();

			}

		} catch (FileNotFoundException e) {
			e.printStackTrace();
			return null;
		} catch (XMLStreamException e) {
			e.printStackTrace();
			return null;
		} catch (LocsiLoadException e) {
			this.setChanged();
			this.notifyObservers(e.getMessage());
			return null;
		}
		return mainCmodel;
	}

	/**
	 * In dieser privaten Methode wird die XML-Datei nach dem zu
	 * <code>circuitName</code> gehoerigen circuit-Tag gesucht und daraus das
	 * cModel konstruiert wobei dabei auch ein rekursiver Aufruf moeglich ist,
	 * sofern wieder ein SD-Bauteil gefunden wird --Macht ansonsten mehr oder
	 * weniger das gleiche wie readCircuit; //evtl noch optimieren und das nur
	 * über eine methode laufen lassen
	 * 
	 * @param path
	 *            - Dateipfad der XML-Datei
	 * @param circuitName
	 *            - Name des selbstdefinierten Bauteils (= Name des
	 *            entsprechenden circuit-Tags)
	 * @return <code>CircuitModel</code> - das CircuitModel des SD-Bauteils
	 * @throws FileNotFoundException
	 *             - wenn Datei nicht gefunden werden konnte
	 * @throws XMLStreamException
	 * @throws LocsiLoadException
	 *             - wenn ein unbekannter Bauteiltyp eingelesen wird
	 */
	private CircuitModel getCmodelOfSelfDefRecursive(String path,
			String circuitName) throws FileNotFoundException,
			XMLStreamException, LocsiLoadException {
		this.listOfCircuitsToLoad.add(circuitName);
		XMLInputFactory factory = XMLInputFactory.newInstance();
		XMLStreamReader parser = factory
				.createXMLStreamReader(new FileInputStream(path));
		CircuitModel sdCmodel = null;
		boolean foundCorrectCircuit = false;
		while (parser.hasNext()) {
			switch (parser.getEventType()) {
			case XMLStreamConstants.START_DOCUMENT:
				break;

			case XMLStreamConstants.END_DOCUMENT:
				parser.close();
				break;

			case XMLStreamConstants.START_ELEMENT:
				String tagName = parser.getLocalName();
				if (tagName.equals("circuit")) {
					if (circuitName.equals(parser.getAttributeValue(null,
							"name"))) {
						sdCmodel = new CircuitModel();
						sdCmodel.setName(circuitName);
						foundCorrectCircuit = true;
					} else {
						foundCorrectCircuit = false;
						break;
					}
				} else if (foundCorrectCircuit && tagName.equals("component")) {
					String type = parser.getAttributeValue(null, "type");
					LogicType lType = xmlToLogicType(type, parser);

					if (lType != LogicType.SELFDEFINED) {
						sdCmodel.addLogicElement(lType, parser
								.getAttributeValue(null, "name"), Double
								.parseDouble(parser.getAttributeValue(null,
										"posx")), Double.parseDouble(parser
								.getAttributeValue(null, "posy")), null);
					} else {

						String circTagName = parser.getAttributeValue(null,
								"type2");

						// überprüfung ob der tag nach dem gesucht wird nicht eh
						// schon in der liste ist
						// falls ja -> endlosschleife
						if (listOfCircuitsToLoad.contains(circTagName)) {
							throw new LocsiLoadException(
									"Fehler beim Laden wegen Abhängigkeiten die nicht verarbeitet werden können!");
						}

						CircuitModel subCmodel = getCmodelOfSelfDefRecursive(
								path, circTagName);
						sdCmodel.addLogicElement(lType, parser
								.getAttributeValue(null, "name"), Double
								.parseDouble(parser.getAttributeValue(null,
										"posx")), Double.parseDouble(parser
								.getAttributeValue(null, "posy")), subCmodel);
					}
				} else if (foundCorrectCircuit && tagName.equals("connection")) {
					String source = parser.getAttributeValue(null, "source");
					String sourcePort = parser.getAttributeValue(null,
							"sourcePort");
					String target = parser.getAttributeValue(null, "target");
					String targetPort = parser.getAttributeValue(null,
							"targetPort");
					sdCmodel.setConnection(source, sourcePort, target,
							targetPort);
				}

				break;

			case XMLStreamConstants.END_ELEMENT:
				break;

			case XMLStreamConstants.NAMESPACE:
				break;

			case XMLStreamConstants.CHARACTERS:
				break;

			default:
				break;
			}
			parser.next();

		}
		this.listOfCircuitsToLoad.remove(circuitName);
		return sdCmodel;
	}

	/**
	 * Gibt zu einer in der XML-Datei gefundenen Typ-Angabe in Stringform den
	 * zugehoerigen <code>LogicType</code> mit dem Locsi arbeiten kann.
	 * 
	 * @param type
	 *            - Typ des Bauteils als String
	 * @param parser
	 *            - XMLParser der bei Bedarf type2 einlesen kann
	 * @return <code>LogicType</code> - einen LogicType wenn das passende
	 *         Bauteil gefunden wurde
	 * @throws LocsiLoadException
	 *             - falls kein passendes Bauteil gefunden werden konnte
	 */
	private LogicType xmlToLogicType(String type, XMLStreamReader parser)
			throws LocsiLoadException {
		if (type.equals("and")) {
			return LogicType.AND;
		} else if (type.equals("or")) {
			return LogicType.OR;
		} else if (type.equals("not")) {
			return LogicType.NOT;
		} else if (type.equals("id")) {
			return LogicType.NODE;
		} else if (type.equals("flipflop")) {
			return LogicType.FLIPFLOP;
		} else if (type.equals("in")) {
			return LogicType.SWITCH;
		} else if (type.equals("out")) {
			return LogicType.LAMP;
		} else if (type.equals("clock")) {
			return LogicType.CLOCK;
		} else if (type.equals("circuit")) {
			return LogicType.SELFDEFINED;
		} else if (type.equals("other")) {
			String type2 = parser.getAttributeValue(null, "type2");
			if (type2 == null) {
				throw new LocsiLoadException(
						"Fehler beim Laden: Fehlerhafte XML-Datei! (type2 nicht definiert)");
			}
			if (type2.equals("xor")) {
				return LogicType.XOR;
			} else if (type2.equals("nand")) {
				return LogicType.NAND;
			} else if (type2.equals("two_mx")) {
				return LogicType.TWO_MX;
			} else {
				throw new LocsiLoadException(
						"Fehler beim Laden: Element vom Typ " + type2
								+ " ist nicht bekannt!");
			}
		} else if (type.equals("missing-circuit")) {
			String type2 = parser.getAttributeValue(null, "type2");
			if (type2 == null) {
				throw new LocsiLoadException(
						"Fehler beim Laden: Fehlerhafte XML-Datei! (type2 nicht definiert)");
			}
			File file = new File(UtilityConstants.SD_DIRECTORY + type2
					+ UtilityDialogs.XML_EXTENSION);
			if (file.exists()) {
				return LogicType.SELFDEFINED;
			} else {
				throw new LocsiLoadException(
						"Fehler beim Laden: Element vom Typ " + type2
								+ " ist nicht bekannt!");
			}
		}
		return null;
	}

	/**
	 * Speichert das uebergebene <code>CircuitModel</code> als selbstdefiniertes
	 * Bauteil. Als Name wird der Name des CircuitModels genommen. Vorher wurde
	 * schon ueberprueft, ob das CircuitModel ueberhaupt vollstaendig ist und
	 * als Bauteil abgespeichert werden kann.
	 * 
	 * Ruft <code>saveCircuit</code> auf.
	 * 
	 * @param model
	 *            <code>CircuitModel</code> - das als selbstdefiniertes Bauteil
	 *            zu speichernde CircuitModel
	 * @return true wenn das speichern erfolgreich war, ansonsten false
	 */
	protected boolean createSelfdefinedModel(CircuitModel model) {
		if (saveCircuit(model.getName(),
				UtilityConstants.SD_DIRECTORY + model.getName()
						+ UtilityDialogs.XML_EXTENSION, model)) {
			eModel.getComponentHandler().refreshComponentList();
			this.setChanged();
			this.notifyObservers("Schaltung erfolgreich als Bauteil '"
					+ model.getName() + "' exportiert!");
			return true;
		}
		this.setChanged();
		this.notifyObservers("Exportieren als Bauteil fehlgeschlagen!");
		return false;
	}

	/**
	 * Speichert das uebergebene <code>CircuitModel</code> als Schaltung.
	 * 
	 * @param path
	 *            <code>String</code> - Pfad an der das Bauteil gespeichert
	 *            werden soll (inklusive Dateiname, egal ob mit oder ohne
	 *            Endung)
	 */
	public boolean saveCircuit(String fileName, String path, CircuitModel cModel) {
		if (fileName == null || path == null || cModel == null
				|| !isNameAllowedInCmodel(fileName, cModel)) {
			return false;
		} else {
			File file = new File(path.split(".xml")[0]
					+ UtilityDialogs.XML_EXTENSION);
			FileWriter writer = null;
			try {
				if (!file.exists()) {
					file.getParentFile().mkdirs();
					file.createNewFile();
				}
				writer = new FileWriter(file);

				writer.write(xmlHead);
				writer.write(createCircuitsTag(fileName.split(".xml")[0]));
				writer.write(createCircuitTag(fileName.split(".xml")[0]));

				StringBuilder connectionTagsToAdd = new StringBuilder();

				cModelListToSave = new ArrayList<CircuitModel>();
				cModelMapToSave = new HashMap<String, CircuitModel>();
				List<LogicEditorElement> mainElements = cModel
						.getElementsListOnEditor();
				String type = "";
				for (LogicEditorElement elem : mainElements) {
					type = logicTypeToXML(elem.getType());

					// wenn ein selbstdefiniertes bauteil gefunden wird, muss
					// das zugehoerige cModel sich gemerkt werden, um es
					// anschließend
					// als weiteren circuitTag hinzuzufuegen
					if (elem.getType() == LogicType.SELFDEFINED) {
						LogicSelfdefinedEditorElement sElem = (LogicSelfdefinedEditorElement) elem;
						if (!cModelMapToSave.containsKey(sElem.getcModel()
								.getName())) {
							cModelListToSave.add(sElem.getcModel());
							cModelMapToSave.put(sElem.getcModel().getName(),
									sElem.getcModel());
						}

						writer.write(createComponentTag(type, sElem.getcModel()
								.getName(), elem.getName(),
								(int) elem.getXCoord() + "",
								(int) elem.getYCoord() + ""));

						// ansonsten wird ein ganz normaler componenttag in die
						// datei
						// geschrieben
					} else {
						if (elem.getType() == LogicType.NAND
								|| elem.getType() == LogicType.XOR
								|| elem.getType() == LogicType.TWO_MX) {
							writer.write(createComponentTag(type, elem
									.getType().name().toLowerCase(),
									elem.getName(),
									(int) elem.getXCoord() + "",
									(int) elem.getYCoord() + ""));
						} else {
							writer.write(createComponentTag(type, null,
									elem.getName(),
									(int) elem.getXCoord() + "",
									(int) elem.getYCoord() + ""));
						}
					}

					// erzeugen des connection tags fuer das momentane element
					// wird dann in den stringBuilder appended
					for (Input inp : elem.getInputs().values()) {
						if (inp.getSourceOutput() != null) {
							connectionTagsToAdd.append(createConnectionTag(
									getElementNameByOutputInCmodel(
											inp.getSourceOutput(), cModel),
									getOutputNameInCircuitModel(
											inp.getSourceOutput(), cModel),
									getElementNameByInputInCmodel(inp, cModel),
									getInputNameInCircuitModel(inp, cModel)));
						}
					}
				}

				// am ende werden die connections der hauptschaltung geaddet
				// und dann alle selbstdefinierten bauteile abgearbeitet und die
				// zugehoerigen circuittags erzeugt
				writer.write(connectionTagsToAdd.toString());
				writer.write(createCircuitEndTag());
				for (int i = 0; i < cModelListToSave.size(); i++) {
					writer.write(createInnerCircuitString(cModelListToSave
							.get(i), cModelListToSave.get(i).getName()));
				}
				writer.write(createCircuitsEndTag());
				writer.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			return true;
		}
	}

	/**
	 * Erstellt einen neuen kompletten XML-Tag fuer ein CircuitModel
	 * 
	 * @param cModel
	 *            <code>CircuitModel</code>, fuer das ein Tag erstellt werden
	 *            soll
	 * @param circuitName
	 *            Name der Schaltung
	 * @return passender XML-String
	 */
	private String createInnerCircuitString(CircuitModel cModel,
			String circuitName) {
		StringBuilder toReturn = new StringBuilder();
		toReturn.append(createCircuitTag(circuitName));
		StringBuilder connectionTagsToAdd = new StringBuilder();

		String type = "";

		for (LogicEditorElement elem : cModel.getElementsListOnEditor()) {
			type = logicTypeToXML(elem.getType());
			if (elem.getType() == LogicType.SELFDEFINED) {
				LogicSelfdefinedEditorElement sElem = (LogicSelfdefinedEditorElement) elem;
				if (!cModelMapToSave.containsKey(sElem.getcModel().getName())) {
					cModelListToSave.add(sElem.getcModel());
					cModelMapToSave.put(sElem.getcModel().getName(),
							sElem.getcModel());
				}

				toReturn.append(createComponentTag(type, sElem.getcModel()
						.getName(), elem.getName(),
						(int) elem.getXCoord() + "", (int) elem.getYCoord()
								+ ""));
			} else {
				if (elem.getType() == LogicType.NAND
						|| elem.getType() == LogicType.XOR
						|| elem.getType() == LogicType.TWO_MX) {
					toReturn.append(createComponentTag(type, elem.getType()
							.name().toLowerCase(), elem.getName(),
							(int) elem.getXCoord() + "", (int) elem.getYCoord()
									+ ""));
				} else {
					toReturn.append(createComponentTag(type, null,
							elem.getName(), (int) elem.getXCoord() + "",
							(int) elem.getYCoord() + ""));
				}
			}

			// erzeugen des connection tags fuer das momentane element
			// wird dann in den stringBuilder appended
			for (Input inp : elem.getInputs().values()) {
				if (inp.getSourceOutput() != null
						&& isOutputInCModel(inp.getSourceOutput(), cModel)
						&& elem.getType() != LogicType.LAMP
						&& elem.getType() != LogicType.SWITCH
						&& elem.getType() != LogicType.CLOCK
						&& !inp.getMySuperElement().equals(
								inp.getSourceOutput().getMySuperElement())) {
					connectionTagsToAdd.append(createConnectionTag(
							getElementNameByOutputInCmodel(
									inp.getSourceOutput(), cModel),
							getOutputNameInCircuitModel(inp.getSourceOutput(),
									cModel),
							getElementNameByInputInCmodel(inp, cModel),
							getInputNameInCircuitModel(inp, cModel)));
				} else if (elem.getType() == LogicType.LAMP) {
					connectionTagsToAdd.append(createConnectionTag(
							getElementNameByOutputInCmodel(
									inp.getSourceOutput(), cModel),
							getOutputNameInCircuitModel(inp.getSourceOutput(),
									cModel), elem.getName(), Input.STD_A));
				} else if (elem.getType() == LogicType.SWITCH
						|| elem.getType() == LogicType.CLOCK) {
					for (Output out : elem.getOutputs().values()) {
						for (Input in : out.getTargetInputs()) {
							connectionTagsToAdd.append(createConnectionTag(
									elem.getName(), Output.STD_O,
									getElementNameByInputInCmodel(in, cModel),
									getInputNameInCircuitModel(in, cModel)));
						}
					}
				}
			}
		}
		toReturn.append(connectionTagsToAdd);
		toReturn.append(createCircuitEndTag());
		return toReturn.toString();
	}

	/**
	 * Prueft ob sich ein bestimmter Output auf einem Bauteil eines bestimmten
	 * cModels befindet. Der Ausgang muss allerdings direkt auf dem cModel sein.
	 * Das hei�t, dass wenn sich ein Ausgang innerhalb eines selbstdefinierten
	 * Bauteils befindet, aber selbst nicht einen Ausgang dieses
	 * selbstdefinierten Bauteils darstellt, wird die Methode false
	 * zurueckgeben.
	 * 
	 * @param out
	 *            - der zu ueberpruefende <code>Output</code>
	 * @param cModel
	 *            - das <code>CircuitModel</code> auf dem der Output liegen soll
	 * @return true, wenn sich der Output direkt im cModel befindet, false sonst
	 */
	private boolean isOutputInCModel(Output out, CircuitModel cModel) {
		for (LogicEditorElement lEE : cModel.getElementsOnEditor().values()) {
			if (lEE.getOutputs().containsValue(out)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Gibt den Namen eines Bauteils zur�ck auf dem sich ein Output befindet
	 * innerhalb eines bestimmten CircuitModels. Achtung: in verschiedenen
	 * cModels kann ein Ausgang zu verschiedenen Bauteilen gehoeren.
	 * 
	 * @param out
	 *            der Output
	 * @param cModel
	 *            das CircuitModel
	 * @return den Namen des Bauteils in dem bestimmten CircuitModel
	 */
	private String getElementNameByOutputInCmodel(Output out,
			CircuitModel cModel) {
		for (LogicEditorElement lEE : cModel.getElementsOnEditor().values()) {
			if (lEE.getOutputs().containsValue(out)) {
				return lEE.getName();
			}
		}
		return out.getMyElement().getName();
	}

	/**
	 * Gibt den Namen eines Bauteils zur�ck auf dem sich ein Input befindet
	 * innerhalb eines bestimmten CircuitModels. Achtung: in verschiedenen
	 * cModels kann ein Eingang zu verschiedenen Bauteilen gehoeren.
	 * 
	 * @param inp
	 *            der Input
	 * @param cModel
	 *            das CircuitModel
	 * @return den Namen des Bauteils in dem bestimmten CircuitModel
	 */
	private String getElementNameByInputInCmodel(Input inp, CircuitModel cModel) {
		for (LogicEditorElement lEE : cModel.getElementsOnEditor().values()) {
			if (lEE.getInputs().containsValue(inp)) {
				return lEE.getName();
			}
		}
		return inp.getMyElement().getName();
	}

	/**
	 * Gibt den Namen eines Outputs zur�ck in einem bestimmten CircuitModel
	 * Achtung: in verschiedenen cModels hat ein Ausgang normalerweise
	 * unterschiedliche Namen
	 * 
	 * @param out
	 *            der betrachtete Output
	 * @param cModel
	 *            das betrachtete CircuitModel
	 * @return den Namen des Ausgangs in dem bestimmten CircuitModel
	 */
	private String getOutputNameInCircuitModel(Output out, CircuitModel cModel) {
		for (LogicEditorElement lEE : cModel.getElementsOnEditor().values()) {
			if (lEE.getOutputs().containsValue(out)) {
				for (Map.Entry<String, Output> outputs : lEE.getOutputs()
						.entrySet()) {
					if (outputs.getValue().equals(out)) {
						return outputs.getKey();
					}
				}
			}
		}
		return out.getName();
	}

	/**
	 * Gibt den Namen eines Inputs zur�ck in einem bestimmten CircuitModel
	 * Achtung: in verschiedenen cModels hat ein Eingang normalerweise
	 * unterschiedliche Namen
	 * 
	 * @param inp
	 *            der betrachtete Input
	 * @param cModel
	 *            das betrachtete CircuitModel
	 * @return den Namen des Eingangs in dem bestimmten CircuitModel
	 */
	private String getInputNameInCircuitModel(Input inp, CircuitModel cModel) {
		for (LogicEditorElement lEE : cModel.getElementsOnEditor().values()) {
			if (lEE.getInputs().containsValue(inp)) {
				for (Map.Entry<String, Input> inputs : lEE.getInputs()
						.entrySet()) {
					if (inputs.getValue().equals(inp)) {
						return inputs.getKey();
					}
				}
			}
		}
		return inp.getName();
	}

	/**
	 * Erstellt einen neuen Circuits Tag
	 * 
	 * @param main
	 *            Name des Tags
	 * @return passender XML-String
	 */
	private String createCircuitsTag(String main) {
		return "<circuits main='"
				+ main
				+ "' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'"
				+ " xmlns='http://www.sosy-lab.org/Teaching/2011-WS-SEP/xmlns/circuits-1.0'>"
				+ UtilityConstants.NEWLINE;
	}

	/**
	 * Erstellt einen neuen Circuit Tag
	 * 
	 * @param name
	 *            Name des Tags
	 * @return passender XML-String
	 */
	private String createCircuitTag(String name) {
		return tab + "<circuit name='" + name + "'>" + UtilityConstants.NEWLINE;
	}

	/**
	 * Erstellt einen neuen Component-Tag
	 * 
	 * @param type
	 *            Typ des Bauteils
	 * @param type2
	 *            Typ2 des Bauteils
	 * @param name
	 *            Name des Bauteils
	 * @param posx
	 *            x-Position des Bauteils
	 * @param posy
	 *            y-Position des Bauteils
	 * @return passender XML-String
	 */
	private String createComponentTag(String type, String type2, String name,
			String posx, String posy) {
		if (type2 == null) {
			return tab + tab + "<component type='" + type + "' name='" + name
					+ "' posx='" + posx + "' posy='" + posy + "'/>"
					+ UtilityConstants.NEWLINE;
		} else {
			return tab + tab + "<component type='" + type + "' type2='" + type2
					+ "' name='" + name + "' posx='" + posx + "' posy='" + posy
					+ "'/>" + UtilityConstants.NEWLINE;
		}
	}

	/**
	 * Erstellt einen neuen Connection-Tag
	 * 
	 * @param source
	 *            Bauteilname des Outputs
	 * @param sourcePort
	 *            Name des Outputs
	 * @param target
	 *            Bauteilname des Inputs
	 * @param targetPort
	 *            Name des Inputs
	 * @return passender XML-String
	 */
	private String createConnectionTag(String source, String sourcePort,
			String target, String targetPort) {
		return tab + tab + "<connection source='" + source + "' sourcePort='"
				+ sourcePort + "' target='" + target + "' targetPort='"
				+ targetPort + "'/>" + UtilityConstants.NEWLINE;
	}

	/**
	 * Erstellt einen Circuits-End-Tag
	 * 
	 * @return passender XML-String
	 */
	private String createCircuitsEndTag() {
		return "</circuits>";
	}

	/**
	 * Erstellt einen Circuit-End-Tag
	 * 
	 * @return passender XML-String
	 */
	private String createCircuitEndTag() {
		return tab + "</circuit>" + UtilityConstants.NEWLINE;
	}

	/**
	 * Wandelt einen <code>LogicType</code> in einen fuer das XML-Format
	 * passenden String um
	 * 
	 * @param type
	 *            LogicType der umgewandelt werden soll
	 * @return passender String fuers XML-Format
	 */
	private String logicTypeToXML(LogicType type) {
		switch (type) {
		case NODE:
			return "id";
		case SWITCH:
			return "in";
		case LAMP:
			return "out";
		case SELFDEFINED:
			return "circuit";
		case XOR:
			return "other";
		case NAND:
			return "other";
		case TWO_MX:
			return "other";
		default:
			return type.name().toLowerCase();
		}
	}

	/**
	 * Validiert eine XML-Datei anhand des XSD-Schemas. Wenn das Schema oder die
	 * Datei nicht existieren wird direkt false zurueckgegeben.
	 * 
	 * @param pathToXMLfile
	 *            - Pfad zur XML-Datei
	 * @return true wenn die Datei dem XSD-Schema entspricht, false sonst
	 */
	private boolean isValidXML(String pathToXMLfile) {
		if (pathToXMLfile == null) {
			throw new IllegalArgumentException("Pfad darf nicht null sein");
		}
		File file = new File(pathToXMLfile);
		Source xmlFile = new StreamSource(file);

		// schemadatei und xmldatei müssen existieren
		if (!schemaFile.exists() || !file.exists()) {
			this.setChanged();
			this.notifyObservers("XSD-Schema bzw. XML-Datei existieren nicht!");
			return false;
		}

		SchemaFactory schemaFactory = SchemaFactory
				.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
		Schema schema = null;
		try {
			schema = schemaFactory.newSchema(schemaFile);
		} catch (SAXException e1) {
			e1.printStackTrace();
			this.setChanged();
			this.notifyObservers("XSD-Schema ungültig!");
			return false;
		}
		Validator validator = schema.newValidator();
		try {
			validator.validate(xmlFile);
			return true;
		} catch (SAXException e) {
			this.setChanged();
			this.notifyObservers(file.getName() + " ist nicht gültig"
					+ UtilityConstants.NEWLINE + "Grund: " + e.getMessage());
			return false;
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * Ueberprueft, ob ein bestimmter Name fuer ein selbstdefiniertes Bauteil
	 * bzw. eine Schaltung erlaubt ist. Es ist beispielsweise nicht erlaubt ein
	 * Bauteil/eine Schaltung so zu nennen wie ein Bauteil das innerhalb des
	 * Bauteils/der Schaltung vorkommt. Es wird auch geprueft ob der Bauteilname
	 * schon bei einem ganz anderen Bauteil verwendet wurde.
	 * 
	 * @param fileName
	 *            - der Dateiname der genutzt werden soll
	 * @param cModel
	 *            - das cModel in dem eventuell selbstdefinierte Bauteile
	 *            vorkommen, die die Namenswahl einschraenken
	 * @return true wenn der Name benutzt werden darf, false sonst
	 */
	private boolean isNameAllowedInCmodel(String fileName, CircuitModel cModel) {

		File sdDir = new File(UtilityConstants.SD_DIRECTORY);
		if (sdDir.exists()) {
			List<String> list = Arrays.asList(sdDir.list());
			if (list.contains(fileName + UtilityDialogs.XML_EXTENSION)) {
				return false;
			}
		}

		for (LogicEditorElement lEE : cModel.getElementsListOnEditor()) {
			if (lEE.getType() == LogicType.SELFDEFINED
					&& (lEE.getName().equals(fileName) || !isNameAllowedInCmodel(
							fileName,
							((LogicSelfdefinedEditorElement) lEE).getcModel()))) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Methode um ein selbstdefiniertes Bauteil in der Bauteilauswahlliste
	 * umzubenennen. Der Benutzer wird gefragt, ob die schon vorhandenen
	 * Bauteile dieses Typs in der Schaltung rückwirkend umbenannt werden
	 * sollen. Falls ja, wird der zugehoerige Screenshot der beim drüberfahren
	 * über das Bauteil angezeigt wird, gelöscht. Ansonsten wird er noch bis zum
	 * Beenden des Programms behalten.
	 * 
	 * @param oldName
	 *            - der alte Name des Bauteils
	 * @param newName
	 *            - der neue Name des Bauteils
	 * @return true falls das umbenennen erfolgreich war, sonst false
	 */
	public boolean renameSelfdefined(String oldName, String newName) {
		File oldFile = new File(UtilityConstants.SD_DIRECTORY + oldName
				+ UtilityDialogs.XML_EXTENSION);

		// zum umbenennen wird das cModel eingelesen, der name des CircuitModel
		// geaendert und unter neuem namen abgespeichert
		CircuitModel model = readSelfDefinedModel(UtilityConstants.SD_DIRECTORY
				+ oldName);
		if (model != null && isNameAllowedInCmodel(newName, model)) {
			model.setName(newName);
			createSelfdefinedModel(model);
			oldFile.delete();

			// die alte picture datei des selfdefined elements muss auch
			// umbenannt werden
			File picFile = new File(UtilityConstants.SD_DIRECTORY + oldName
					+ UtilityDialogs.PNG_EXTENSION);
			if (picFile.exists()) {
				File newFile = new File(UtilityConstants.SD_DIRECTORY + newName
						+ UtilityDialogs.PNG_EXTENSION);

				FileChannel inChannel;
				FileChannel outChannel;
				try {
					inChannel = new FileInputStream(picFile).getChannel();
					outChannel = new FileOutputStream(newFile).getChannel();
					inChannel.transferTo(0, inChannel.size(), outChannel);
					inChannel.close();
					outChannel.close();
				} catch (FileNotFoundException e) {
					e.printStackTrace();
					this.setChanged();
					this.notifyObservers("Die zugehörige Bilddatei '"
							+ picFile.getName() + "' ist nicht vorhanden!");
				} catch (IOException e) {
					this.setChanged();
					this.notifyObservers("Fehler beim Lesen bzw. Schreiben!");
					e.printStackTrace();
				}
				picFile.deleteOnExit();
				return true;
			} else {
				this.setChanged();
				this.notifyObservers("Die zugehörige Bilddatei '"
						+ picFile.getName() + "' ist nicht vorhanden!");
				return true;
			}
		} else {
			return false;
		}
	}
}
